package net.avcompris.binding.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.util.HashMap;
import java.util.Map;

import net.avcompris.binding.BindConfiguration;
import net.avcompris.binding.Binder;
import net.avcompris.binding.BinderFilter;

/**
 * filter on an existing binder that ensures only one object is created to stub
 * a given node.
 * 
 * @author David Andrianavalontsalama
 */
public class UniqueBinder<U> extends AbstractBinder<U> implements
		BinderFilter<U> {

	public UniqueBinder(final Binder<U> delegate) {

		this.delegate = nonNullArgument(delegate, "delegate");

		if (!AbstractBinder.class.isInstance(delegate)) {
			throw new IllegalArgumentException(
					"delegate should be an instance of AbstractBinder<U>");
		}

		((AbstractBinder<U>) delegate).setThis(this);
	}

	@Override
	public Binder<U> getDelegate() {

		return delegate;
	}

	private final Binder<U> delegate;

	private final Map<U, Object> proxies = new HashMap<U, Object>();

	@Override
	protected <T> T bindInterface(final BindConfiguration configuration,
			final U node, final ClassLoader classLoader, final Class<T> clazz) {

		nonNullArgument(node, "node");

		Object proxy = proxies.get(node);

		if (proxy == null) {

			proxy = createProxy(configuration, node, classLoader, clazz);
		}

		return clazz.cast(proxy);
	}

	private synchronized <T> Object createProxy(
			final BindConfiguration configuration, final U node,
			final ClassLoader classLoader, final Class<T> clazz) {

		nonNullArgument(configuration, "configuration");
		nonNullArgument(node, "node");
		nonNullArgument(classLoader, "classLoader");
		nonNullArgument(clazz, "clazz");

		Object proxy = proxies.get(node);

		if (proxy == null) {

			proxy = delegate.bind(configuration, node, classLoader, clazz);

			proxies.put(node, proxy);
		}

		return clazz.cast(proxy);
	}
}
